#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QFileIconProvider>
#include <QMessageBox>
#include <QFileDialog>
#include <QListWidgetItem>
#include <QDirIterator>
#include <QThread>
#include <QMenu>
#include <QActionGroup>
#include "op/KcPickSameFile.h"
#include "op/KcPickGrayPhoto.h"
#include "op/KcPickSimilarPhoto.h"
#include "op/KcPickFacePhoto.h"
#include "util/QuFile.h"
#include "util/QuLayout.h"

enum KePickOp
{
    k_pick_same_file,
    k_pick_similar_photo,
    k_pick_gray_photo,
    k_pick_face_photo
};

enum KeSelectOp
{
    k_select_none = 0,
    k_select_all,
    k_select_reverse,
    k_select_first_items,
    k_select_last_items
};

namespace kPrivate
{
    static QThread* statsThread{ nullptr }; // 统计线程
    static std::unique_ptr<KvPickOp> pickOp_;

    // 辅助函数，把与btn关联（名称等于id）的菜单项设置为checked状态
    static void checkMenuItem(QToolButton* btn, int id, bool checked)
    {
        for (auto act : btn->menu()->actions())
            if (id == act->objectName().toInt())
                act->setChecked(checked);
    }
}

MainWindow::MainWindow(QWidget *parent)
    : super_(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    /// 配置工具栏
    ///
    ui->toolBar->addWidget(ui->tbSameFile);
    ui->toolBar->addWidget(ui->tbSimiPhoto);
    ui->toolBar->addWidget(ui->tbGrayPhoto);
    ui->toolBar->addWidget(ui->tbFacePhoto);
    ui->tbSameFile->setChecked(true);
    setWindowTitle("找茬 - 重复文件");

    /// 配置初始界面
    ///
    updateUi_(k_pick_ready);

    /// 设置手型光标
    ///
    ui->tbSameFile->setCursor(Qt::PointingHandCursor);
    ui->tbSimiPhoto->setCursor(Qt::PointingHandCursor);
    ui->tbGrayPhoto->setCursor(Qt::PointingHandCursor);
    ui->tbFacePhoto->setCursor(Qt::PointingHandCursor);
    ui->btnStart->setCursor(Qt::PointingHandCursor);
    ui->btnAddPath->setCursor(Qt::PointingHandCursor);
    ui->btnDelPath->setCursor(Qt::PointingHandCursor);
    ui->btnOpenPath->setCursor(Qt::PointingHandCursor);
    ui->btnAddAllDrivers->setCursor(Qt::PointingHandCursor);
    ui->btnAddUserHome->setCursor(Qt::PointingHandCursor);
    ui->btnCopy->setCursor(Qt::PointingHandCursor);
    ui->btnCut->setCursor(Qt::PointingHandCursor);
    ui->btnRemove->setCursor(Qt::PointingHandCursor);
    ui->cbSubDirs->setCursor(Qt::PointingHandCursor);

    /// 配置菜单项
    ///
    auto viewModeMenu = new QMenu;
    auto actGroup = new QActionGroup(viewModeMenu);
    actGroup->addAction("列表")->setObjectName(QString::number(QcPickResultView::k_view_list));
    actGroup->addAction("小图标")->setObjectName(QString::number(QcPickResultView::k_view_small_icon));
    actGroup->addAction("中图标")->setObjectName(QString::number(QcPickResultView::k_view_median_icon));
    actGroup->addAction("大图标")->setObjectName(QString::number(QcPickResultView::k_view_large_icon));
    viewModeMenu->addActions(actGroup->actions());
    viewModeMenu->addSeparator();
    auto actShowThumb = new QAction("缩略图");
    actShowThumb->setCheckable(true);
    viewModeMenu->addAction(actShowThumb);
    auto actShowText = new QAction("显示文本");
    actShowText->setCheckable(true);
    viewModeMenu->addAction(actShowText);
    ui->tbViewMode->setMenu(viewModeMenu);
    for (auto i : actGroup->actions())
        i->setCheckable(true);
    ui->resultView->setViewMode(QcPickResultView::k_view_list);
    kPrivate::checkMenuItem(ui->tbViewMode, QcPickResultView::k_view_list, true);

    auto selectMenu = new QMenu;
    selectMenu->addAction(QIcon(":/icon/img/select-all.svg"), "全部勾选")->setObjectName(QString::number(k_select_all));
    selectMenu->addAction("选中首项")->setObjectName(QString::number(k_select_first_items));
    selectMenu->addAction("选中末项")->setObjectName(QString::number(k_select_last_items));
    selectMenu->addAction(QIcon(":/icon/img/select-reverse.svg"), "反向选择")->setObjectName(QString::number(k_select_reverse));
    selectMenu->addAction("取消勾选")->setObjectName(QString::number(k_select_none));
    ui->tbSelect->setMenu(selectMenu);

    auto sortMenu = new QMenu;
    auto sortGroup = new QActionGroup(sortMenu);
    for (int i = 0; i < ui->resultView->columnCount(); i++)
        sortGroup->addAction("按" + ui->resultView->columnName(i))->setObjectName(QString::number(i));
    sortMenu->addActions(sortGroup->actions());
    sortMenu->addSeparator();
    auto orderGroup = new QActionGroup(sortMenu);
    orderGroup->addAction(QIcon(":/icon/img/sort-ascending.svg"), "升序")->setObjectName(QString::number(Qt::AscendingOrder + 100));
    orderGroup->addAction(QIcon(":/icon/img/sort-descending.svg"), "降序")->setObjectName(QString::number(Qt::DescendingOrder + 100));
    sortMenu->addActions(orderGroup->actions());
    ui->tbSort->setMenu(sortMenu);
    for (auto i : sortGroup->actions()) i->setCheckable(true);
    for (auto i : orderGroup->actions()) i->setCheckable(true);

    /// 链接各类信号
    ///
    connect(ui->tbSameFile, &QToolButton::toggled, this, &MainWindow::on_toolbutton_toggled);
    connect(ui->tbSimiPhoto, &QToolButton::toggled, this, &MainWindow::on_toolbutton_toggled);
    connect(ui->tbGrayPhoto, &QToolButton::toggled, this, &MainWindow::on_toolbutton_toggled);
    connect(ui->tbFacePhoto, &QToolButton::toggled, this, &MainWindow::on_toolbutton_toggled);

    connect(ui->btnAddPath, &QToolButton::clicked, ui->listWorkspace, &QcPickWorkspace::on_new_path);
    connect(ui->btnDelPath, &QToolButton::clicked, ui->listWorkspace, &QcPickWorkspace::on_remove_path);
    connect(ui->btnOpenPath, &QToolButton::clicked, ui->listWorkspace, &QcPickWorkspace::on_open_path);

    connect(ui->listWorkspace, &QcPickWorkspace::itemDoubleClicked, ui->listWorkspace, &QcPickWorkspace::on_open_path);
    connect(ui->listWorkspace, &QcPickWorkspace::currentRowChanged, this, [this](int currentRow) {
        ui->btnDelPath->setEnabled(currentRow >= 0);
        ui->btnOpenPath->setEnabled(currentRow >= 0);
    });

    // checked状态变换信号，更新勾选相关的统计变量
    connect(ui->resultView, &QcPickResultView::checkedFilesChanged, this, &MainWindow::on_checkedFiles_changed);

    // resultwidget相关的3个toolbutton触发信号
    connect(ui->tbViewMode, &QToolButton::triggered, this, [=](QAction *arg1) { // 切换视图模式
        if (arg1 == actShowThumb)
            ui->resultView->setUseThumbnail(arg1->isChecked());
        else if (arg1 == actShowText)
            ui->resultView->setShowDisplayRole(arg1->isChecked());
        else
            ui->resultView->setViewMode(QcPickResultView::KeViewMode(arg1->objectName().toInt()));
    });
    connect(ui->tbSelect, &QToolButton::triggered, this, [this](QAction *arg1) { // 勾选
        execSelectOp_(arg1->objectName().toInt());
    });
    connect(ui->tbSort, &QToolButton::triggered, this, [this](QAction *arg1) { // 排序
        auto id = arg1->objectName().toInt();
        if (id < 100) {
            ui->resultView->sort(id, ui->resultView->sortOrder());
        }
        else {
            auto col = ui->resultView->sortColumn();
            auto order = Qt::SortOrder(id - 100);
            if (col >= 0)
                ui->resultView->sort(col, order);
            else
                ui->resultView->setSortOrder(order);
        }
    });

    // 动态更新排序菜单项的checked状态
    connect(sortMenu, &QMenu::aboutToShow, this, [=](){
        for (auto i : sortGroup->actions())
            i->setChecked(i->objectName().toInt() == ui->resultView->sortColumn());
        for (auto i : orderGroup->actions())
            i->setChecked(i->objectName().toInt() - 100 == ui->resultView->sortOrder());
    });

    connect(viewModeMenu, &QMenu::aboutToShow, this, [=](){
        actShowThumb->setChecked(ui->resultView->useThumbnail());
        actShowText->setChecked(ui->resultView->showDisplayRole());
        actShowText->setEnabled(ui->resultView->viewMode() > QcPickResultView::k_view_list );
    });
}

MainWindow::~MainWindow()
{
    if (kPrivate::pickOp_)
        kPrivate::pickOp_.reset(); // 显示删除，不然有可能与其他lib的静态成员销毁冲突

    delete ui;
}


KvPickOp* MainWindow::makePickOp_()
{
    if (ui->tbSimiPhoto->isChecked())
        return new KcPickSimilarPhoto(ui->sdSimilarity->value() / 100.);

    if (ui->tbGrayPhoto->isChecked()) {
        auto tol = 1. - ui->sdSimilarity->value() / 100.;
        return new KcPickGrayPhoto(tol * 0.4, tol); // 0.4拍脑袋值
    }

    if (ui->tbFacePhoto->isChecked()) {
        return new KcPickFacePhoto(ui->sdSimilarity->value() / 100.);
    }

    Q_ASSERT(ui->tbSameFile->isChecked());
    return new KcPickSameFile();
}

void MainWindow::execSelectOp_(int type)
{
    switch(type)
    {
    case k_select_none:
        if (ui->resultView->groupCount() > 0) {
            for (int i = 0; i < ui->resultView->groupCount(); i++)
                for (int j = 0; j < ui->resultView->groupSize(i); j++)
                    ui->resultView->setItemChecked(i, j, false);
        }
        else {
            for (int j = 0; j < ui->resultView->itemAmount(); j++)
                ui->resultView->setItemChecked(-1, j, false);
        }
        break;

    case k_select_all:
        if (ui->resultView->groupCount() > 0) {
            for (int i = 0; i < ui->resultView->groupCount(); i++)
                for (int j = 0; j < ui->resultView->groupSize(i); j++)
                    ui->resultView->setItemChecked(i, j, true);
        }
        else {
            for (int j = 0; j < ui->resultView->itemAmount(); j++)
                ui->resultView->setItemChecked(-1, j, true);
        }
        break;

    case k_select_reverse:
        if (ui->resultView->groupCount() > 0) {
            for (int i = 0; i < ui->resultView->groupCount(); i++)
                for (int j = 0; j < ui->resultView->groupSize(i); j++)
                    ui->resultView->setItemChecked(i, j, !ui->resultView->isItemChecked(i, j));
        }
        else {
            for (int j = 0; j < ui->resultView->itemAmount(); j++)
                ui->resultView->setItemChecked(-1, j, !ui->resultView->isItemChecked(-1, j));
        }
        break;

    case k_select_first_items:
        if (ui->resultView->groupCount() > 0) {
            for (int i = 0; i < ui->resultView->groupCount(); i++)
                if (ui->resultView->groupSize(i) > 0)
                    ui->resultView->setItemChecked(i, 0, true);
        }
        else {
            ui->resultView->setItemChecked(-1, 0, true);
        }
        break;

    case k_select_last_items:
        if (ui->resultView->groupCount() > 0) {
            for (int i = 0; i < ui->resultView->groupCount(); i++)
                if (ui->resultView->groupSize(i) > 0)
                    ui->resultView->setItemChecked(i, ui->resultView->groupSize(i) - 1, true);
        }
        else {
            ui->resultView->setItemChecked(-1, ui->resultView->itemAmount() - 1, true);
        }
        break;
    }
}

void MainWindow::on_sdSimilarity_valueChanged(int value)
{
    ui->lbSimilarity->setText(QString("%1%").arg(value));
}

void MainWindow::on_checkedFiles_changed(int checkedFiles, qint64 totalSize)
{
    Q_ASSERT (pickState_() == k_pick_finished);

    if (checkedFiles == 1)
        ui->actionGroup->setVisible(true);
    else if (checkedFiles == 0)
        ui->actionGroup->setVisible(false);

    if (checkedFiles > 0) {
        QString msg;
        QTextStream(&msg) << "选中了 " << checkedFiles << " 个文件,  共 <font color=blue>"
                          << locale().formattedDataSize(totalSize)
                          << "</font>";
        ui->lbSelectStats->setText(msg);
    }
}


void MainWindow::on_btnCopy_clicked()
{
    auto destFolder = QFileDialog::getExistingDirectory(this, "选择目录");
    if (!destFolder.isEmpty()) {
        auto fn = [destFolder](const QString& filePath) {
            QuFile::copy(filePath, destFolder);
        };

        ui->resultView->forEachCheckedItem(fn, -1);
    }
}


void MainWindow::on_btnCut_clicked()
{
    auto destFolder = QFileDialog::getExistingDirectory(this, "选择目录");
    if (destFolder.isEmpty())
        return;

    auto fn = [destFolder](const QString& filePath) {
        QuFile::move(filePath, destFolder);
    };

    ui->resultView->forEachCheckedItem(fn, 1);
}


void MainWindow::on_btnRemove_clicked()
{
    auto res = QMessageBox::question(this, "提示", "确认删除选中的文件？", QMessageBox::Yes | QMessageBox::No);
    if (res == QMessageBox::No)
        return;

    auto fn = [](const QString& filePath) {
        QFile::moveToTrash(filePath);
    };

    ui->resultView->forEachCheckedItem(fn, 1);
}

#include <QMutex>
void MainWindow::switchState_(KePickState state)
{
    static QMutex mt; // 用于同步pick状态的转换

    mt.lock();

    if (state != state_) {
        bool validSwitch(true);
        if (state_ == k_pick_ready) {
            if (state == k_pick_preparing)
                startPreparing_(); // ready->preparing.
            else {
                validSwitch = false;
                Q_ASSERT(false); // ready态只能转换到preparing态
            }
        }
        else if (state_ == k_pick_preparing) {
            if (state == k_pick_running) // preparing->running.
                startPicking_(); // NB: 确保pick线程启动（哪怕工作空间为null），以保证结束时正常发出finished信号
            else if (state == k_pick_finished || state == k_pick_ready) {
                stopPreparing_();
                Q_ASSERT(kPrivate::statsThread->isFinished());
            }
        }
        else if (state_ == k_pick_running) {
            if (state == k_pick_finished || state == k_pick_ready)
                stopPicking_();
            else {
                validSwitch = false;
                Q_ASSERT(false);
            }
        }
        else if (state_ == k_pick_finished) {
            validSwitch = (state == k_pick_ready);
        }

        if (validSwitch) {
            updateUi_(state);
            state_ = state;
        }
    }

    mt.unlock();
}

void MainWindow::on_btnStart_clicked()
{
    auto state = pickState_();

    if (state == k_pick_ready)
        switchState_(k_pick_preparing);
    else if (state == k_pick_running)
        switchState_(k_pick_finished);
    else // (state == k_pick_finished || state == k_pick_preparing)
        switchState_(k_pick_ready);
}

void MainWindow::startPreparing_()
{
    Q_ASSERT (state_ == k_pick_ready);

    // 清空结果列表
    ui->resultView->reset();
    filesTotal_ = filesPicked_ = filesRead_ = filesDone_ = filesFailed_ = 0;

    // 创建并启动统计线程，主要搜集待扫描的文件信息
    kPrivate::statsThread = QThread::create([this](){
        QSet<QString> workspace;
        for(int i = 0; i < ui->listWorkspace->count(); i++)
            workspace.insert(ui->listWorkspace->item(i)->text());

        while(!workspace.isEmpty()) {

            if (QThread::currentThread()->isInterruptionRequested())
                break;

            auto path = *workspace.begin();
            workspace.erase(workspace.begin());

            auto files = QuFile::totalFiles(path, false);
            QMetaObject::invokeMethod(this, "updateTotalFiles", Qt::QueuedConnection, Q_ARG(int, files));

            if (ui->cbSubDirs->isChecked()) {
                QDirIterator dirIter(path, QDir::Dirs | QDir::NoDotAndDotDot);
                while(dirIter.hasNext())
                    workspace.insert(dirIter.next());
            }
        }
    });

    connect(kPrivate::statsThread, &QThread::finished, this, [this](){
        if (pickState_() == k_pick_preparing)
            switchState_(k_pick_running);
        sender()->deleteLater();
    });

    kPrivate::statsThread->start();
}

QString MainWindow::formatResultStats_() const
{
    QString msg = ui->resultView->groupCount() > 0 ? "%1组%2" : "%1";
    if (ui->resultView->groupCount() > 0)
        msg = msg.arg(ui->resultView->groupCount());
    return msg.arg(ui->resultView->itemAmount());
}

QString MainWindow::formatMsgTitle_() const
{
    QString title;
    QTextStream(&title) << "扫描 " << filesPicked_ << "/" << filesTotal_ << ", "
                        << "已处理 " << filesDone_ << ", "
                        << "待处理 " << kPrivate::pickOp_->queued() << ", "
                        << "找到 " << formatResultStats_() << ", "
                        << "失败 " << filesFailed_;
    return title;
}

void MainWindow::startPicking_()
{
    Q_ASSERT (state_ == k_pick_preparing);

    // 根据用户选择的类别执行扫描操作
    kPrivate::pickOp_.reset(makePickOp_());

    // 反找茬?
    kPrivate::pickOp_->setPickNonHoles(ui->cbReverse->isChecked());

    // NB: 尝试了queue和direct两种连接模式
    // - direct模式下，会出现2类异常：
    // 1. ASSERT失败（QCoreApplication::sendEvent），cannot send events to objects owned
    // by a different thread. (Receiver 'qt_scrollarea_vcontainer' was created in different thread).
    // 2. release版本下bulebtn和redbtn的样式失效（debug版本未见此情况），原因不明
    // - queue模式下，后期主线程阻塞，初步判断为QTreeWidget插入更新引起（一定数据量情况下，由icon模式切换到list模式也很慢）
    // 另，大数据量情况下，由list模式切换为icon模式时，程序崩溃（原因不明）
    //
    // v0.1.4版本重构了model/view，可确保大数据量（10万级）下的运行稳定性和视图模式切换的流畅性（排序和展开/收缩仍较慢）
    connect(kPrivate::pickOp_.get(), &KvPickOp::resultFound, this, [this](int group, const QString& path){
        ui->resultView->appendItem(group, path);
        }, Qt::DirectConnection);

    connect(kPrivate::pickOp_.get(), &KvPickOp::finished, this, [this](){
        showStatusMessage_(formatMsgTitle_(), "请选择需要清理的文件", false);
        ui->resultView->showView(true); // 主要expandAll，并设置treeView的首列扩展
        switchState_(k_pick_finished); // 同步更新ui状态
    }, Qt::QueuedConnection);

    connect(kPrivate::pickOp_.get(), &KvPickOp::filePicked, this, [this](const QString& filePath){
        ++filesPicked_;
        showStatusMessage_(formatMsgTitle_(), filePath, false);
    }, Qt::QueuedConnection);

    connect(kPrivate::pickOp_.get(), &KvPickOp::fileRead, this, [this](const QString& filePath, void* obj) {
            if (obj) filesRead_++;
            else filesFailed_++;
            showStatusMessage_(formatMsgTitle_(), filePath, false);
    }, Qt::QueuedConnection);

    connect(kPrivate::pickOp_.get(), &KvPickOp::fileDone, this, [this](const QString& filePath) {
            filesDone_++;
            // 等待队列发生变化，更新messge
            showStatusMessage_(formatMsgTitle_(), filePath, false);
        }, Qt::QueuedConnection);

    QSet<QString> workspace;
    for(int i = 0; i < ui->listWorkspace->count(); i++)
        workspace.insert(ui->listWorkspace->item(i)->text());

    kPrivate::pickOp_->start(workspace, ui->cbSubDirs->isChecked());
}

void MainWindow::stopPreparing_()
{
    kPrivate::statsThread->requestInterruption();
    kPrivate::statsThread->wait();
}

void MainWindow::stopPicking_()
{
    if (kPrivate::pickOp_)
        kPrivate::pickOp_->stop(true);
}

void MainWindow::closeEvent(QCloseEvent *e)
{
    if (kPrivate::pickOp_)
        kPrivate::pickOp_->stop(true);

    return super_::closeEvent(e);
}

void MainWindow::on_toolbutton_toggled(bool checked)
{
    if (checked) {
        // 更新配置区域的ui
        updateConfig_(pickState_());

        // 更新app标题
        auto toolbtn = dynamic_cast<QToolButton*>(sender());
        if (toolbtn && toolbtn->isChecked()) {
            QString title("找茬 - ");
            this->setWindowTitle(title + toolbtn->text());
        }
    }
}

void MainWindow::updateUi_(KePickState state)
{
    ui->btnStart->setText(state == k_pick_ready ? tr("开找") :
                              state == k_pick_finished ? tr("返回") : tr("停止"));

    // 刷新工具栏按钮的状态
    bool busy = (state == k_pick_preparing || state == k_pick_running);
    ui->tbSameFile->setEnabled(!busy || ui->tbSameFile->isChecked());
    ui->tbSimiPhoto->setEnabled(!busy || ui->tbSimiPhoto->isChecked());
    ui->tbGrayPhoto->setEnabled(!busy || ui->tbGrayPhoto->isChecked());
    ui->tbFacePhoto->setEnabled(!busy || ui->tbFacePhoto->isChecked());

    updateConfig_(state);

    ui->workspaceGroup->setVisible(state == k_pick_ready);

    ui->busyWidget->setVisible(busy);
    if (busy)
        ui->busyWidget->startAnimation();
    else
        ui->busyWidget->stopAnimation();

    ui->resultGroup->setVisible(state == k_pick_finished);
    ui->actionGroup->setVisible(false); // 始终不可见，后面处理check状态变化信号会更新可见性

    if (state == k_pick_ready) // TODO: 有时信息不能及时显示，导致外部显示与内部状态不一致
        showStatusMessage_("请设定扫描路径", "", false); // NB: 此处updateUi标记不能为true，否则会导致宕机（preparing->ready）
}

void MainWindow::updateConfig_(KePickState state)
{
    ui->similarityGroup->setVisible(state == k_pick_ready &&
                                    (ui->tbGrayPhoto->isChecked()
                                  || ui->tbSimiPhoto->isChecked()
                                  || ui->tbFacePhoto->isChecked()));

    ui->portraitGroup->setVisible(false); // 暂时用不上

    ui->cbReverse->setVisible(state == k_pick_ready &&
                              (ui->tbGrayPhoto->isChecked()
                               || ui->tbFacePhoto->isChecked()));
}

void MainWindow::showStatusMessage_(const QString& title, const QString& body, bool updateUi)
{
    QString msg;
    if (!title.isEmpty()) {
        msg = "<big>";
        msg += title;
        msg += "</font></big>";

        if (!body.isEmpty())
            msg += "<br>";
    }

    if (!body.isEmpty()) {
        msg += "<font color=#5f6264>";
        msg += body;
        msg += "</font>";
    }

    ui->lbMsg->setText(msg);

    if (updateUi)  // TODO: 非主线程能调用该方法吗？
        QApplication::processEvents();
}

void MainWindow::on_btnAddAllDrivers_clicked()
{
    auto di = QDir::drives();
    for (auto i : di) {
        if (i.isReadable())
            ui->listWorkspace->addPath(i.path());
    }
}


void MainWindow::on_btnAddUserHome_clicked()
{
    ui->listWorkspace->addPath(QDir::homePath());
}


void MainWindow::updateTotalFiles(int moreFiles)
{
    filesTotal_ += moreFiles;
    if (kPrivate::statsThread->isRunning()) // NB: 加入该判断，否则可能会破坏状态信息（比如停止preparing操作后，会重写掉ready状态信息）
        showStatusMessage_("正在搜集扫描路径信息", QString("发现待扫描文件 %1 个").arg(filesTotal_), false);
}
